refactor(trace/semantics): lift conditional gRPC status fallback into mappings.json#50397
Conversation
|
🎯 Code Coverage (details) 🔗 Commit SHA: fd8b5e5 | Docs | Datadog PR Page | Give us feedback! |
Files inventory check summaryFile checks results against ancestor d76c9679: Results for datadog-agent_7.80.0~devel.git.622.fd8b5e5.pipeline.112189231-1_amd64.deb:No change detected |
Static quality checks✅ Please find below the results from static quality gates Successful checksInfo
14 successful checks with minimal change (< 2 KiB)
|
Regression DetectorRegression Detector ResultsMetrics dashboard Baseline: 9de9473 Optimization Goals: ✅ No significant changes detected
|
| perf | experiment | goal | Δ mean % | Δ mean % CI | trials | links |
|---|---|---|---|---|---|---|
| ➖ | docker_containers_cpu | % cpu utilization | -1.57 | [-4.45, +1.30] | 1 | Logs |
Fine details of change detection per experiment
| perf | experiment | goal | Δ mean % | Δ mean % CI | trials | links |
|---|---|---|---|---|---|---|
| ➖ | quality_gate_logs | % cpu utilization | +1.74 | [+0.76, +2.72] | 1 | Logs bounds checks dashboard |
| ➖ | ddot_metrics_sum_cumulative | memory utilization | +0.36 | [+0.20, +0.52] | 1 | Logs |
| ➖ | ddot_metrics_sum_cumulativetodelta_exporter | memory utilization | +0.32 | [+0.09, +0.56] | 1 | Logs |
| ➖ | otlp_ingest_metrics | memory utilization | +0.31 | [+0.16, +0.47] | 1 | Logs |
| ➖ | otlp_ingest_logs | memory utilization | +0.26 | [+0.16, +0.36] | 1 | Logs |
| ➖ | quality_gate_idle_all_features | memory utilization | +0.16 | [+0.12, +0.20] | 1 | Logs bounds checks dashboard |
| ➖ | tcp_syslog_to_blackhole | ingress throughput | +0.13 | [-0.06, +0.31] | 1 | Logs |
| ➖ | file_to_blackhole_1000ms_latency | egress throughput | +0.03 | [-0.42, +0.49] | 1 | Logs |
| ➖ | uds_dogstatsd_to_api_v3 | ingress throughput | +0.01 | [-0.19, +0.21] | 1 | Logs |
| ➖ | tcp_dd_logs_filter_exclude | ingress throughput | -0.00 | [-0.09, +0.09] | 1 | Logs |
| ➖ | file_to_blackhole_0ms_latency | egress throughput | -0.01 | [-0.55, +0.53] | 1 | Logs |
| ➖ | uds_dogstatsd_to_api | ingress throughput | -0.02 | [-0.21, +0.18] | 1 | Logs |
| ➖ | file_to_blackhole_100ms_latency | egress throughput | -0.03 | [-0.17, +0.12] | 1 | Logs |
| ➖ | file_to_blackhole_500ms_latency | egress throughput | -0.04 | [-0.45, +0.36] | 1 | Logs |
| ➖ | ddot_logs | memory utilization | -0.05 | [-0.12, +0.02] | 1 | Logs |
| ➖ | ddot_metrics_sum_delta | memory utilization | -0.10 | [-0.28, +0.09] | 1 | Logs |
| ➖ | uds_dogstatsd_20mb_12k_contexts_20_senders | memory utilization | -0.20 | [-0.24, -0.15] | 1 | Logs |
| ➖ | quality_gate_idle | memory utilization | -0.20 | [-0.25, -0.15] | 1 | Logs bounds checks dashboard |
| ➖ | file_tree | memory utilization | -0.42 | [-0.47, -0.37] | 1 | Logs |
| ➖ | ddot_metrics | memory utilization | -0.44 | [-0.63, -0.24] | 1 | Logs |
| ➖ | docker_containers_memory | memory utilization | -0.48 | [-0.58, -0.38] | 1 | Logs |
| ➖ | quality_gate_metrics_logs | memory utilization | -0.75 | [-1.00, -0.50] | 1 | Logs bounds checks dashboard |
| ➖ | docker_containers_cpu | % cpu utilization | -1.57 | [-4.45, +1.30] | 1 | Logs |
Bounds Checks: ✅ Passed
| perf | experiment | bounds_check_name | replicates_passed | observed_value | links |
|---|---|---|---|---|---|
| ✅ | docker_containers_cpu | simple_check_run | 10/10 | 707 ≥ 26 | |
| ✅ | docker_containers_memory | memory_usage | 10/10 | 244.71MiB ≤ 370MiB | |
| ✅ | docker_containers_memory | simple_check_run | 10/10 | 723 ≥ 26 | |
| ✅ | file_to_blackhole_0ms_latency | memory_usage | 10/10 | 0.16GiB ≤ 1.20GiB | |
| ✅ | file_to_blackhole_0ms_latency | missed_bytes | 10/10 | 0B = 0B | |
| ✅ | file_to_blackhole_1000ms_latency | memory_usage | 10/10 | 0.21GiB ≤ 1.20GiB | |
| ✅ | file_to_blackhole_1000ms_latency | missed_bytes | 10/10 | 0B = 0B | |
| ✅ | file_to_blackhole_100ms_latency | memory_usage | 10/10 | 0.17GiB ≤ 1.20GiB | |
| ✅ | file_to_blackhole_100ms_latency | missed_bytes | 10/10 | 0B = 0B | |
| ✅ | file_to_blackhole_500ms_latency | memory_usage | 10/10 | 0.18GiB ≤ 1.20GiB | |
| ✅ | file_to_blackhole_500ms_latency | missed_bytes | 10/10 | 0B = 0B | |
| ✅ | quality_gate_idle | intake_connections | 10/10 | 3 ≤ 4 | bounds checks dashboard |
| ✅ | quality_gate_idle | memory_usage | 10/10 | 140.89MiB ≤ 147MiB | bounds checks dashboard |
| ✅ | quality_gate_idle_all_features | intake_connections | 10/10 | 3 ≤ 4 | bounds checks dashboard |
| ✅ | quality_gate_idle_all_features | memory_usage | 10/10 | 471.23MiB ≤ 495MiB | bounds checks dashboard |
| ✅ | quality_gate_logs | intake_connections | 10/10 | 4 ≤ 6 | bounds checks dashboard |
| ✅ | quality_gate_logs | memory_usage | 10/10 | 179.15MiB ≤ 195MiB | bounds checks dashboard |
| ✅ | quality_gate_logs | missed_bytes | 10/10 | 0B = 0B | bounds checks dashboard |
| ✅ | quality_gate_metrics_logs | cpu_usage | 10/10 | 348.53 ≤ 2000 | bounds checks dashboard |
| ✅ | quality_gate_metrics_logs | intake_connections | 10/10 | 3 ≤ 6 | bounds checks dashboard |
| ✅ | quality_gate_metrics_logs | memory_usage | 10/10 | 368.07MiB ≤ 430MiB | bounds checks dashboard |
| ✅ | quality_gate_metrics_logs | missed_bytes | 10/10 | 0B = 0B | bounds checks dashboard |
Explanation
Confidence level: 90.00%
Effect size tolerance: |Δ mean %| ≥ 5.00%
Performance changes are noted in the perf column of each table:
- ✅ = significantly better comparison variant performance
- ❌ = significantly worse comparison variant performance
- ➖ = no significant change in performance
A regression test is an A/B test of target performance in a repeatable rig, where "performance" is measured as "comparison variant minus baseline variant" for an optimization goal (e.g., ingress throughput). Due to intrinsic variability in measuring that goal, we can only estimate its mean value for each experiment; we report uncertainty in that value as a 90.00% confidence interval denoted "Δ mean % CI".
For each experiment, we decide whether a change in performance is a "regression" -- a change worth investigating further -- if all of the following criteria are true:
-
Its estimated |Δ mean %| ≥ 5.00%, indicating the change is big enough to merit a closer look.
-
Its 90.00% confidence interval "Δ mean % CI" does not contain zero, indicating that if our statistical model is accurate, there is at least a 90.00% chance there is a difference in performance between baseline and comparison variants.
-
Its configuration does not mark it "erratic".
CI Pass/Fail Decision
✅ Passed. All Quality Gates passed.
- quality_gate_idle_all_features, bounds check memory_usage: 10/10 replicas passed. Gate passed.
- quality_gate_idle_all_features, bounds check intake_connections: 10/10 replicas passed. Gate passed.
- quality_gate_idle, bounds check intake_connections: 10/10 replicas passed. Gate passed.
- quality_gate_idle, bounds check memory_usage: 10/10 replicas passed. Gate passed.
- quality_gate_logs, bounds check memory_usage: 10/10 replicas passed. Gate passed.
- quality_gate_logs, bounds check missed_bytes: 10/10 replicas passed. Gate passed.
- quality_gate_logs, bounds check intake_connections: 10/10 replicas passed. Gate passed.
- quality_gate_metrics_logs, bounds check memory_usage: 10/10 replicas passed. Gate passed.
- quality_gate_metrics_logs, bounds check intake_connections: 10/10 replicas passed. Gate passed.
- quality_gate_metrics_logs, bounds check missed_bytes: 10/10 replicas passed. Gate passed.
- quality_gate_metrics_logs, bounds check cpu_usage: 10/10 replicas passed. Gate passed.
… mappings.json Move the rpc.system.name/rpc.system → rpc.response.status_code special case out of transform.getOTelGRPCStatusCode and into the semantic registry as `when` clauses on TagInfo. This removes the gRPC-specific helper from the transform package and lets future conditional fallbacks be expressed declaratively in mappings.json. Conditions read attributes by exact key (no transitive concept resolution) — to gate on a renamed attribute, list one fallback row per condition attribute. This matches the dd-source trace-semantics precedent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the float64-precision sub-test from TestLookupFloat64; it tests a structural property unrelated to conditional fallback that this PR doesn't touch. - Drop the conditional.eq cases; the gRPC test cases already exercise Eq accept/reject on a realistic concept. - Drop the conditional.present/conditional.absent cases; Present is unused in mappings.json today and the AND test covers Present:true implicitly. - Rename testBoolPtr to boolPtr (the _test.go suffix already conveys it is test-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nal check inline
Drop the lookupWithConditions/readTag/readFloat64Tag/readInt64Tag
extraction. The conditional fallback feature only needs a single
`if !conditionsMatch(...) { continue }` line at the top of each loop;
extracting per-type readers and a generic helper was orthogonal
churn that obscured the diff.
Net diff vs main: +37 lines, 0 deletions to existing code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pre-refactor getOTelGRPCStatusCode used GetOTelAttrFromEitherMap with
keys ("rpc.system.name", "rpc.system"), which returns the first non-empty
key — so when an SDK sets rpc.system.name=jsonrpc and (incorrectly) also
rpc.system=grpc, the new key wins and rpc.response.status_code is rejected.
Without this fix the two independent registry rows would each evaluate
against their own attribute, and the legacy rpc.system=grpc row would
match — accepting a status code that pre-PR rejected.
Gate the legacy rows on rpc.system.name being absent so the new semconv
key remains authoritative when both are present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TestConditionalLookup now uses NewEmbeddedRegistry() against the real mappings.json instead of a hand-rolled fake. Removes ~50 lines of Provider/Type struct-literal boilerplate and the synthetic conditional.and concept (the rpc.system+rpc.system.name two-condition AND in production gives implicit AND coverage). boolPtr helper is no longer needed. - Collapse the duplicated `c.Present != nil` check at the tail of conditionMatches into a single return. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…C keys
rpc.response.status_code and rpc.system.name were both first added to
OTel semantic-conventions in v1.39.0 (Jan 2026), not v1.24.0 (Dec 2023).
Verified against the upstream repo:
- rpc.response.status_code: commit cd5b45c6 (2025-12-04), .chloggen/rpc-status-code.yaml
"Deprecate per-rpc status code attributes in favor of rpc.response.status_code"
- rpc.system.name: commit 18db3e44 (2025-12-09), .chloggen/rpc-system-name.yaml
"Align RPC conventions with naming guidelines. Renames rpc.system to rpc.system.name."
Both commits land in v1.39.0 (published 2026-01-12). model/registry/rpc.yaml
at v1.24.0 has neither attribute.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull the "no declared predicate" default to the top with a comment, then check each declared predicate independently. Removes the trailing `return c.Present != nil || found` boolean fold whose meaning was easy to mis-read. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single-letter local was idiomatic but unclear in context. Both locals now self-document: `value := accessor.GetString(c.Attribute)` and `found := value != ""`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
791811e to
f5d39d4
Compare
…odeConditionalFallback The test only exercises the rpc.grpc.status_code mapping and its four conditional rows, not the conditional machinery in general. Updated name matches the actual scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…onMatches
A Condition with neither Present nor Eq set is unused in mappings.json
today, and the special-case "fall back to presence check" branch was
the most confusing line in the function (per reviewer feedback).
Treat an empty Condition as a no-op (always true), consistent with how
an empty `when: []` array also returns true (zero-iteration loop is
vacuously true). Authors who want a presence check write
{"attribute": "X", "present": true} explicitly.
Function shrinks from 14 to 11 lines; doc comment now states the
semantics explicitly without referring to a degenerate path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…predicate The vast majority of fallback rows in mappings.json have no `when` clause; calling conditionsMatch on those rows just iterates an empty slice and returns true. Adding `len(tag.When) > 0 &&` at each call site makes the conditional structure visible at the lookup site (most rows unconditional, some conditional) and avoids the dead function call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ffd9f7abe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "rpc.system": { | ||
| "canonical": "rpc.system", | ||
| "fallbacks": [ | ||
| {"name": "rpc.system.name", "provider": "otel", "version": "1.39.0", "type": "string"}, |
There was a problem hiding this comment.
Add release note for rpc.system.name operation naming
When operation/resource name v2 is enabled, getOTelOperationNameV2 reads ConceptRPCSystem (see pkg/trace/transform/otelutil.go:444), so adding rpc.system.name here makes OTLP spans that only carry the new semconv key produce RPC operation names such as grpc.server.request instead of falling back to the instrumentation-scope name. That is a user-visible behavior change separate from the existing gRPC-status-code reno, so the PR should document it or avoid changing this fallback in the status-code refactor.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — verified and applied. Reverted the rpc.system.name addition to the rpc.system concept's fallback chain in b16f23e.
The gRPC conditional rows on rpc.grpc.status_code read rpc.system.name/rpc.system as raw attribute keys in their when clauses, so they don't depend on the rpc.system concept's fallback chain. Removing this entry has zero effect on gRPC status routing while restoring getOTelOperationNameV2's pre-PR behavior — so no release-note change is needed for this PR. The rpc.system.name promotion is a separate refactor; I've restored the future-work bullet in semantics.go to track it.
Codex bot flagged that adding rpc.system.name to the rpc.system concept's fallback chain has a side effect via getOTelOperationNameV2 (transform/otelutil.go:444), which uses LookupString(ConceptRPCSystem) to drive RPC operation naming. With the fallback added, OTLP spans carrying only the new semconv key would have produced operation names like "grpc.server.request" instead of falling back to the instrumentation-scope name — a user-visible change unrelated to the gRPC status-code refactor and not covered by the existing reno. Revert the rpc.system concept's fallback chain to its main state. The gRPC conditional rows in mappings.json read rpc.system.name and rpc.system as raw attribute keys in their `when` clauses, not via the concept fallback chain, so this revert has zero effect on the gRPC status-code routing. Restore the corresponding future-work bullet in semantics.go now that the canonical/fallback promotion is again pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0ffd9f7 to
b16f23e
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4d956587a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| {"name": "grpc.status.code", "provider": "datadog", "type": "int64"}, | ||
| {"name": "rpc.response.status_code", "provider": "otel", "version": "1.39.0", "type": "string", "when": [{"attribute": "rpc.system.name", "eq": "grpc"}]}, | ||
| {"name": "rpc.response.status_code", "provider": "otel", "version": "1.39.0", "type": "int64", "when": [{"attribute": "rpc.system.name", "eq": "grpc"}]}, | ||
| {"name": "rpc.response.status_code", "provider": "otel", "version": "1.39.0", "type": "string", "when": [{"attribute": "rpc.system", "eq": "grpc"}, {"attribute": "rpc.system.name", "present": false}]}, |
There was a problem hiding this comment.
Preserve span-level legacy gRPC fallback before resource keys
When an OTLP span has legacy rpc.system=grpc on the span but the resource includes any non-empty rpc.system.name, this present:false condition fails because OTelSpanAccessor.GetString("rpc.system.name") also checks resource attributes. The removed helper checked both rpc.system.name and rpc.system in span attributes before consulting resource attributes, so mixed-version payloads like span-level rpc.system=grpc plus resource-level rpc.system.name=jsonrpc used to emit rpc.grpc.status_code and now silently drop it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch, but this only triggers when span and resource disagree about the RPC system — like span saying rpc.system=grpc while resource says rpc.system.name=jsonrpc. We don't have evidence any SDK actually does this; happy to revisit if we see it surface in real traffic.
Also, this conforms with how we handle HTTPStatusCodes today
| return false | ||
| } | ||
| if c.Eq != nil { | ||
| if !found || value != fmt.Sprint(c.Eq) { |
There was a problem hiding this comment.
Running fmt.Sprint on every condition match seems unnecessary? I would suggest changing Condition.Eq from any to *string, or precompute the stringified form at registry load time.
There was a problem hiding this comment.
Good call — applied as *string in 0afceb1.
|
cc @mialcheng |
Per @ichinaski's review feedback. Eq is only ever set to a string in mappings.json (verified: 4 occurrences, all "grpc"), and conditionMatches only does string comparison anyway. Typing it as *string lets us drop the per-call fmt.Sprint allocation in conditionMatches and surfaces a loud unmarshal error if anyone tries to write a non-string eq value in JSON, which is the correct fail-loud behavior for the schema. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4d95658 to
0afceb1
Compare
… mappings.json (#50397) ### What does this PR do? Moves the `rpc.response.status_code` → gRPC special case from `transform.go` into the semantics registry as `when` clauses on `TagInfo`. The `transform` package now has zero gRPC-specific code — `LookupString(reg, spanAccessor, ConceptGRPCStatusCode)` does it all. ### Motivation - [DSEM-807](https://datadoghq.atlassian.net/browse/DSEM-807) — Semantics Core team asked for conditional fallback support in the trace-agent registry. - Follow-up to #50272 (merged) — addresses the [reviewer ask](#50272 (comment)) that the gRPC predicate belongs in the registry, not the transformer. - Follows the [dd-source trace-semantics precedent](https://github.com/DataDog/dd-source/blob/main/domains/semantic-core/libs/go/trace-semantics/mappings.json#L803), which already expresses the same predicate. ### Describe how you validated your changes ``` go test ./pkg/trace/semantics/... -count=1 # 8 sub-tests in TestGRPCStatusCodeConditionalFallback go test ./pkg/trace/transform/... -count=1 # no behavioral change go test ./pkg/trace/otel/stats/... -count=1 # OTLP gRPC fixture still populates rpc.grpc.status_code ``` CI handles the broader integration suites. ### Additional Notes **Known minor divergence from #50272:** the merged PR's `getOTelGRPCStatusCode` walked per-source (span-then-resource, both keys per source); our registry walks per-key (`OTelSpanAccessor` does span-then-resource per key independently). Both check span and resource — only the axis ordering differs. I don't see this as an issue because this is how we handle HTTPStatusCodes today ``` Pre-PR (getOTelGRPCStatusCode): walk per-source first, picking newer key within a source: span: rpc.system.name? rpc.system? → first hit wins res: rpc.system.name? rpc.system? → first hit wins (only if span empty) Post-PR (registry, OTelSpanAccessor): walk per-key first, picking span over resource for each key: rpc.system.name: span? res? → first hit wins rpc.system: span? res? → first hit wins (only if rpc.system.name empty) ``` | File | What changed | |---|---| | `semantics/semantics.go` | `Condition` type + `When []Condition` on `TagInfo` | | `semantics/lookup.go` | `conditionMatches` / `conditionsMatch` helpers; 1-line guard in each `Lookup*` | | `semantics/mappings.json` | 4 conditional `rpc.response.status_code` rows under `rpc.grpc.status_code`; `rpc.system.name` added to `rpc.system` chain | | `semantics/lookup_test.go` | New `TestGRPCStatusCodeConditionalFallback` (8 sub-tests) | | `transform/transform.go` | Delete `getOTelGRPCStatusCode`; call `LookupString` directly | **Design:** - Conditions read attributes by **exact key** — no transitive concept resolution. To gate on a renamed attribute, list one fallback row per condition attribute. - The four `rpc.response.status_code` rows are: (string / int64) × (`rpc.system.name=grpc` / `rpc.system=grpc` AND `rpc.system.name` absent). The absence guard preserves the pre-refactor "new key wins" behavior. - `Condition` schema: `{attribute, present, eq}`. Conditions in a `when` array are AND-ed. **`version: "1.39.0"` provenance:** both new attributes are from OTel semconv [v1.39.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.39.0/model/rpc/registry.yaml) (2026-01-12); neither is in [v1.24.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/model/registry/rpc.yaml). Introducing commits: [`rpc.response.status_code`](open-telemetry/semantic-conventions@cd5b45c6) (cd5b45c6, 2025-12-04) and [`rpc.system.name`](open-telemetry/semantic-conventions@18db3e44) (18db3e44, 2025-12-09). **Release note:** none added — user-visible behavior matches #50272's. The existing `apm-otlp-grpc-status-code-bf3137b34cb82136.yaml` reno still applies. [DSEM-807]: https://datadoghq.atlassian.net/browse/DSEM-807?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ Co-authored-by: edward.hu <edward.hu@datadoghq.com>
What does this PR do?
Moves the
rpc.response.status_code→ gRPC special case fromtransform.gointo the semantics registry aswhenclauses onTagInfo. Thetransformpackage now has zero gRPC-specific code —LookupString(reg, spanAccessor, ConceptGRPCStatusCode)does it all.Motivation
Describe how you validated your changes
CI handles the broader integration suites.
Additional Notes
Known minor divergence from #50272: the merged PR's
getOTelGRPCStatusCodewalked per-source (span-then-resource, both keys per source); our registry walks per-key (OTelSpanAccessordoes span-then-resource per key independently). Both check span and resource — only the axis ordering differs.I don't see this as an issue because this is how we handle HTTPStatusCodes today
semantics/semantics.goConditiontype +When []ConditiononTagInfosemantics/lookup.goconditionMatches/conditionsMatchhelpers; 1-line guard in eachLookup*semantics/mappings.jsonrpc.response.status_coderows underrpc.grpc.status_code;rpc.system.nameadded torpc.systemchainsemantics/lookup_test.goTestGRPCStatusCodeConditionalFallback(8 sub-tests)transform/transform.gogetOTelGRPCStatusCode; callLookupStringdirectlyDesign:
rpc.response.status_coderows are: (string / int64) × (rpc.system.name=grpc/rpc.system=grpcANDrpc.system.nameabsent). The absence guard preserves the pre-refactor "new key wins" behavior.Conditionschema:{attribute, present, eq}. Conditions in awhenarray are AND-ed.version: "1.39.0"provenance: both new attributes are from OTel semconv v1.39.0 (2026-01-12); neither is in v1.24.0. Introducing commits:rpc.response.status_code(cd5b45c6, 2025-12-04) andrpc.system.name(18db3e44, 2025-12-09).Release note: none added — user-visible behavior matches #50272's. The existing
apm-otlp-grpc-status-code-bf3137b34cb82136.yamlreno still applies.